﻿using BackupContracts;
using BackupContracts.Sql;
using Microsoft.Expression.Interactivity.Core;
using Silverlight.DataSources;
using System;
using System.Linq;
using System.Collections.ObjectModel;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Windows.Browser;
using BackupContracts.Tasks;

namespace Silverlight.ViewModel
{
    public class MainPageViewModel : ModelBase
    {
        private NavigationService NavigationService { get; set; }
        public ICommand LoginCommand { get; private set; }
        private DispatcherTimer _UpdateTimer;
        private int _UpdateInterval = 10;

        public MainPageViewModel(Page page)
        {
            AllDatabases = new ObservableCollection<SqlDatabase>();
            AllDatabaseBackups = new ObservableCollection<SqlDatabaseBackup>();
            AllDatabaseBackupLogs = new ObservableCollection<SqlDatabaseBackupLog>();
            AllTasks = new ObservableCollection<Task>();
            AllServers = new ObservableCollection<SqlServer>();
            SelectableDatabases = new ObservableCollection<SelectableSqlDatabase>();

            if (page != null)
            {
                NavigationService = page.NavigationService;
            }
            LoginCommand = new ActionCommand(OnLoginCommand);
            UpdateCommand = new ActionCommand(OnUpdateCommand);
            DownloadBackupCommand = new ActionCommand(OnDownloadBackupCommand);
            NewTaskCommand = new ActionCommand(OnNewTaskCommand);
            SaveTaskCommand = new ActionCommand(OnSaveTaskCommand);
            DeleteTaskCommand = new ActionCommand(OnDeleteTaskCommand);
            AddTaskActionCommand = new ActionCommand(OnAddTaskActionCommand);
            RemoveTaskActionCommand = new ActionCommand(OnRemoveTaskActionCommand);
            AddTaskScheduleCommand = new ActionCommand(OnAddTaskScheduleCommand);
            RemoveTaskScheduleCommand = new ActionCommand(OnRemoveTaskScheduleCommand);
            AddSqlServerCommand = new ActionCommand(OnAddSqlServerCommand);
            RemoveSqlServerCommand = new ActionCommand(OnRemoveSqlServerCommand);
            EditSqlServerCommand = new ActionCommand(OnEditSqlServerCommand);

            _UpdateTimer = new DispatcherTimer();
            _UpdateTimer.Interval = TimeSpan.FromSeconds(1);
            _UpdateTimer.Tick += _UpdateTimer_Tick;
        }

        private TaskAction _SelectedTaskAction;
        public TaskAction SelectedTaskAction
        {
            get { return _SelectedTaskAction; }
            set { SetField(ref _SelectedTaskAction, value, () => SelectedTaskAction); }
        }

        private Schedule _SelectedTaskSchedule;
        public Schedule SelectedTaskSchedule
        {
            get { return _SelectedTaskSchedule; }
            set { SetField(ref _SelectedTaskSchedule, value, () => SelectedTaskSchedule); }
        }

        private void OnRemoveTaskActionCommand(object obj)
        {
            if (EditingTask != null && SelectedTaskAction != null)
            {
                EditingTask.Actions.Remove(SelectedTaskAction);
            }
        }

        private async void OnAddTaskActionCommand(object obj)
        {
            if (EditingTask != null)
            {
                var result = await SelectDatabaseDialog.ShowModal();
                if (result != null)
                {
                    foreach (var database in result.Databases)
                    {
                        database.ServerId = database.Server.Id;
                        EditingTask.Actions.Add(new SqlBackupTaskAction()
                        {
                            Database = database,
                        });
                    }
                }
            }
        }

        private async void OnRemoveSqlServerCommand(object obj)
        {
            if (obj != null)
            {
                await BackupServiceDataSource.Instance.RemoveSqlServerAsync(obj as SqlServer);
                AllServers.Remove(obj as SqlServer);
                await LoadAllServersAsync();
            }
        }

        private async void OnAddSqlServerCommand(object obj)
        {
            var result = await EditSqlServerDialog.ShowModal(new SqlServer()
            {
                Id = Guid.NewGuid().ToString(),
            });
            if (result != null)
            {
                await BackupServiceDataSource.Instance.SaveSqlServerAsync(result.SqlServer);
                AllServers.Add(result.SqlServer);
                await LoadAllServersAsync();
            }
        }

        private async void OnEditSqlServerCommand(object obj)
        {
            var existingSqlServer = obj as SqlServer;
            var result = await EditSqlServerDialog.ShowModal(new SqlServer()
            {
                Id = existingSqlServer.Id,
                Name = existingSqlServer.Name,
                User = existingSqlServer.User,
            });
            if (result != null)
            {
                await BackupServiceDataSource.Instance.SaveSqlServerAsync(result.SqlServer);
                await LoadAllServersAsync();
            }
        }

        private void OnRemoveTaskScheduleCommand(object obj)
        {
            if (EditingTask != null && SelectedTaskSchedule != null)
            {
                EditingTask.Schedules.Remove(SelectedTaskSchedule);
            }
        }

        private void OnAddTaskScheduleCommand(object obj)
        {
            if (EditingTask != null)
            {
                EditingTask.Schedules.Add(new Schedule()
                {
                    Start = new DateTimeOffset(DateTime.Now.Date),
                    RecurEveryX = 1,
                    ScheduleType = ScheduleType.Daily,
                });
            }
        }

        private async void OnDeleteTaskCommand(object obj)
        {
            if (EditingTask != null)
            {
                var task = EditingTask;
                var result = await BackupServiceDataSource.Instance.DeleteTaskAsync(task);
                if (result)
                {
                    SelectedTask = null;
                    await LoadTasksAsync();
                }
                else
                {
                    DisplayErrorMessage("Error on deleting task.");
                }
            }
        }

        private void DisplayErrorMessage(string message)
        {
            MessageBox.Show(message);
        }

        private async void OnSaveTaskCommand(object obj)
        {
            if (EditingTask != null)
            {
                var task = EditingTask;
                var result = await BackupServiceDataSource.Instance.SaveTaskAsync(task);
                if (result)
                {
                    SelectedTask = null;
                    await LoadTasksAsync();
                    var existingTask = AllTasks.FirstOrDefault((t) => t.Id.Equals(task.Id));
                    EditingTask = existingTask;
                }
                else
                {
                    DisplayErrorMessage("Error on saving task.");
                }
            }
        }

        private void OnNewTaskCommand(object obj)
        {
            SelectedTask = null;
            EditingTask = new Task()
            {
                Id = Guid.NewGuid().ToString(),
                Name = "New Task",
            };
        }

        private async void OnDownloadBackupCommand(object obj)
        {
            var url = (string)obj;
            if (string.IsNullOrEmpty(url))
                return;
            url = await BackupServiceDataSource.Instance.GetPublicBlobUrl(url);
            HtmlPage.Window.Navigate(new Uri(url), "_blank");
        }

        private async void OnUpdateCommand(object obj)
        {
            await Update();
        }

        async void _UpdateTimer_Tick(object sender, EventArgs e)
        {
            UpdateStatus = string.Format("Refresh in {0} second(s)", SecondsToUpdate);
            if (SecondsToUpdate == 0)
            {
                await Update();
            }
            else
            {
                SecondsToUpdate--;
            }
        }

        private bool _IsRefreshEnabled = true;
        public bool IsRefreshEnabled
        {
            get { return _IsRefreshEnabled; }
            set { SetField(ref _IsRefreshEnabled, value, () => IsRefreshEnabled); }
        }

        private async System.Threading.Tasks.Task Update()
        {
            _UpdateTimer.Stop();
            IsRefreshEnabled = false;
            SecondsToUpdate = _UpdateInterval;
            UpdateStatus = "Refreshing...";
            try
            {
                var loadTasksTask = LoadTasksAsync();
                var loadServersTask = LoadAllServersAsync();
                await LoadDatabasesAsync();
                await LoadDatabaseBackupsAsync();
                await LoadDatabaseBackupLogsAsync();
                await loadTasksTask;
                await loadServersTask;
                UpdateStatus = "Refreshed!";
            }
            catch (Exception e)
            {
                UpdateStatus = "Error on refreshing!";
            }
            IsRefreshEnabled = true;
            _UpdateTimer.Start();
        }

        private string _UpdateStatus;
        public string UpdateStatus
        {
            get { return _UpdateStatus; }
            set { SetField(ref _UpdateStatus, value, () => UpdateStatus); }
        }

        private int _SecondsToUpdate;
        public int SecondsToUpdate
        {
            get { return _SecondsToUpdate; }
            set { SetField(ref _SecondsToUpdate, value, () => SecondsToUpdate); }
        }

        private async void OnLoginCommand(object obj)
        {
            BackupServiceDataSource.Instance.User = LoginUser;
            BackupServiceDataSource.Instance.Password = LoginPassword;

            var authenticated = await BackupServiceDataSource.Instance.AuthenticateAsync();
            if (authenticated)
            {
                NavigationService.Navigate(new Uri("/BackupManager", UriKind.Relative));
            }
            else
            {
                MessageBox.Show("Invalid username/password.");
            }
        }

        private string _LoginUser;
        public string LoginUser
        {
            get { return _LoginUser; }
            set { SetField(ref _LoginUser, value, () => LoginUser); }
        }

        private string _LoginPassword;
        public string LoginPassword
        {
            get { return _LoginPassword; }
            set { SetField(ref _LoginPassword, value, () => LoginPassword); }
        }


        public async System.Threading.Tasks.Task LoadAsync()
        {
            _UpdateTimer.Start();
        }

        public static void MergeLists<T>(IList<T> baseList, IEnumerable<T> list, Func<T, object> idSelector, Action<T, T> updateAction)
        {
            if (list == null)
                return;

            lock (baseList)
            {
                foreach (var item in list)
                {
                    if (!baseList.Any((i) => object.Equals(idSelector(i), idSelector(item))))
                        baseList.Add(item);
                }
                foreach (var item in list)
                {
                    var existingItem = baseList.FirstOrDefault((i) => object.Equals(idSelector(i), idSelector(item)));
                    if (existingItem != null)
                    {
                        updateAction(existingItem, item);
                    }
                }
                foreach (var item in baseList.ToList())
                {
                    if (!list.Any((i) => object.Equals(idSelector(i), idSelector(item))))
                        baseList.Remove(item);
                }
            }
        }

        public async System.Threading.Tasks.Task LoadDatabasesAsync()
        {
            var allDatabases = await BackupServiceDataSource.Instance.ListAllDatabasesAsync(null);
            MergeLists(AllDatabases, allDatabases, (s) => string.Format("{0}|{1}", s.Server.Name, s.Name), (s1, s2) =>
            {
                s1.LastBackup = s2.LastBackup;
                s1.Status = s2.Status;
            });
            if (!AllDatabases.Contains(SelectedDatabase))
                SelectedDatabase = AllDatabases.FirstOrDefault();
        }

        public async System.Threading.Tasks.Task LoadTasksAsync()
        {
            var allDatabases = await BackupServiceDataSource.Instance.ListAllTasksAsync();
            MergeLists(AllTasks, allDatabases, (s) => s.Id, (s1, s2) =>
            {
                UpdateTask(s1, s2);
            });
        }

        private static void UpdateTask(Task s1, Task s2)
        {
            s1.Actions = s2.Actions;
            s1.Enabled = s2.Enabled;
            s1.LastExecution = s2.LastExecution;
            s1.LastExecutionStart = s2.LastExecutionStart;
            s1.Name = s2.Name;
            s1.Schedules = s2.Schedules;
        }

        public async System.Threading.Tasks.Task LoadDatabaseBackupsAsync()
        {
            if (SelectedDatabase != null)
            {
                var allDatabaseBackups = await BackupServiceDataSource.Instance.ListAllDatabaseBackupsAsync(SelectedDatabase.Server.Name, SelectedDatabase.Name);
                MergeLists(AllDatabaseBackups, allDatabaseBackups.OrderByDescending((b) => b.Start), (s) => s.Id, (s1, s2) =>
                {
                    s1.Start = s2.Start;
                    s1.Finish = s2.Finish;
                    s1.BackupUrl = s2.BackupUrl;
                    s1.Status = s2.Status;
                });
            }
            if (!AllDatabaseBackups.Contains(SelectedBackup))
                SelectedBackup = AllDatabaseBackups.FirstOrDefault();
        }

        public async System.Threading.Tasks.Task LoadDatabaseBackupLogsAsync()
        {
            if (SelectedBackup != null)
            {
                var allDatabaseBackupLogs = await BackupServiceDataSource.Instance.ListAllDatabaseBackupLogsAsync(SelectedBackup.Database.Server.Name, SelectedBackup.Database.Name, SelectedBackup.Id);
                MergeLists(AllDatabaseBackupLogs, allDatabaseBackupLogs.OrderByDescending((b) => b.Date), (s) => s.Id, (s1, s2) =>
                {
                });
            }
        }

        private SqlDatabaseBackup _SelectedBackup;
        public SqlDatabaseBackup SelectedBackup
        {
            get { return _SelectedBackup; }
            set
            {
                if (SetField(ref _SelectedBackup, value, () => SelectedBackup))
                {
                    AllDatabaseBackupLogs.Clear();
                    Update();
                };
            }
        }

        private SqlDatabase _SelectedDatabase;
        public SqlDatabase SelectedDatabase
        {
            get { return _SelectedDatabase; }
            set
            {
                if (SetField(ref _SelectedDatabase, value, () => SelectedDatabase))
                {
                    AllDatabaseBackups.Clear();
                    Update();
                }
            }
        }

        private Task _SelectedTask;
        public Task SelectedTask
        {
            get { return _SelectedTask; }
            set
            {
                if (SetField(ref _SelectedTask, value, () => SelectedTask))
                {
                    if (value != null && !EditingTask.Id.Equals(value.Id))
                    {
                        EditingTask = CloneTask(value);
                    }
                }
            }
        }

        private Task CloneTask(Task value)
        {
            var task = new Task()
            {
                Name = value.Name,
                Enabled = value.Enabled,
                Id = value.Id,
                LastExecution = value.LastExecution,
                LastExecutionStart = value.LastExecutionStart,
                Actions = new ObservableCollection<TaskAction>(),
                Schedules = new ObservableCollection<Schedule>(),
            };
            foreach (var action in value.Actions)
            {
                task.Actions.Add(action);
            }
            foreach (var schedule in value.Schedules)
            {
                task.Schedules.Add(schedule);
            }
            return task;
        }

        private Task _EditingTask;
        public Task EditingTask
        {
            get { return _EditingTask; }
            set { SetField(ref _EditingTask, value, () => EditingTask); }
        }

        public ObservableCollection<SqlDatabase> AllDatabases { get; private set; }
        public ObservableCollection<SelectableSqlDatabase> SelectableDatabases { get; private set; }
        public ObservableCollection<SqlDatabaseBackup> AllDatabaseBackups { get; private set; }
        public ObservableCollection<SqlDatabaseBackupLog> AllDatabaseBackupLogs { get; private set; }
        public ObservableCollection<Task> AllTasks { get; private set; }
        public ObservableCollection<SqlServer> AllServers { get; private set; }

        public ICommand UpdateCommand { get; private set; }
        public ICommand DownloadBackupCommand { get; private set; }
        public ICommand NewTaskCommand { get; private set; }
        public ICommand SaveTaskCommand { get; set; }
        public ICommand DeleteTaskCommand { get; set; }
        public ICommand AddTaskActionCommand { get; set; }
        public ICommand RemoveTaskActionCommand { get; set; }
        public ICommand AddTaskScheduleCommand { get; set; }
        public ICommand RemoveTaskScheduleCommand { get; set; }
        public ICommand AddSqlServerCommand { get; set; }
        public ICommand RemoveSqlServerCommand { get; set; }
        public ICommand EditSqlServerCommand { get; set; }

        public async System.Threading.Tasks.Task LoadAllServersAsync()
        {
            var allServers = await BackupServiceDataSource.Instance.ListAllServersAsync();
            MergeLists(AllServers, allServers, (s) => s.Id, (s1, s2) =>
            {
                s1.Name = s2.Name;
                s1.User = s2.User;
            });
        }

        private SqlServer _SelectedServer;
        public SqlServer SelectedServer
        {
            get { return _SelectedServer; }
            set
            {
                SetField(ref _SelectedServer, value, () => SelectedServer);
                LoadSelectableDatabasesAsync();
            }
        }

        private async void LoadSelectableDatabasesAsync()
        {
            SelectableDatabases.Clear();
            if (SelectedServer != null)
            {
                var allDatabases = await BackupServiceDataSource.Instance.ListAllSqlDatabases(SelectedServer.Id);
                foreach (var database in allDatabases)
                {
                    SelectableDatabases.Add(new SelectableSqlDatabase()
                    {
                        Database = database
                    });
                }
            }
        }



    }
}
